<?php
// @formatter:off
/**
 * @file WCComic.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-06-16 19:41:09 +0000 (Sun, 16 Jun 2013) $
 *
 * $Id: WCComic.php 72 2013-06-16 19:41:09Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0-BETA1/includes/representations/WCComic.php $
 */
// @formatter:on

/**
 * @class WCComic
 * This class represents entries saved in the table "%comics".
 */
class WCComic extends WCGTableRepresentation {
	/**
	 * @var string[] This variable holds the list of columns that cannot be
	 * directly modified by the pseudo-method __set().
	 * In this case cmc_id, cmc_group, cmc_directory and cmc_name.
	 */
	protected $_PropertiesReadOnly = array(
		"id",
		"group",
		"directory",
		"name"
	);
	/**
	 * @var string[] This variable holds the list of columns that must be
	 * treated as booleans and then send to the database as 'Y' and 'N' by
	 * the pseudo-method __set().
	 * In this case cmc_checked, cmc_generated, cmc_indexed, cmc_rated and
	 * cmc_lost.
	 */
	protected $_PropertiesBoolean = array(
		"checked",
		"generated",
		"indexed",
		"rated",
		"lost"
	);
	/**
	 * @var string This is the prefix of every column on the table
	 * '%comics'.
	 */
	protected $_PropertiesPrefix = "cmc_";
	/**
	 * @var string This is the name of the represented table. In this case,
	 * '%comics'.
	 */
	protected $_PropertiesTable = "comics";
	/**
	 * @var WCCollection This is a pointer to the collection that contains
	 * this comic.
	 */
	protected $_collection = null;
	/**
	 * @var WCCollection This is a pointer to the directory where this comic
	 * is located.
	 */
	protected $_directory = null;
	/**
	 * @var object This is the list of images contained by this comic.
	 */
	protected $_images = array();
	/**
	 * @var string This is the root path of the group that contains this
	 * comic.
	 */
	protected $_root;
	/**
	 * @var boolean This flag indicates that full paths and URIs for previews
	 * were loaded or not.
	 */
	protected $_previewLoaded;
	/**
	 * @var string This is the full path to the directory where all comic
	 * thumbnails will be stored. For example:
	 *	- /var/www/wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc
	 */
	protected $_previewDirPath;
	/**
	 * @var string This is similar to '$_previewDirPath', but it is the URI.
	 * It is a full path relative to the server. For example:
	 *	- /wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc
	 */
	protected $_previewDirUri;
	/**
	 * @var string This is the image full path of the comic's thumbnail. For
	 * example:
	 *	- /var/www/wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc.png
	 */
	protected $_previewPath;
	/**
	 * @var string This is similar to '$_previewPath', but it is the URI.
	 * It is a full path relative to the server. For example:
	 *	- /wcomix/cache/thumbs/1/7/007/7007efdc84400474bc19ed442dbfeccc.png
	 */
	protected $_previewUri;
	/**
	 * @var boolean This flag indicates that the full-path and URI to the
	 * expanded directory were loaded or not.
	 */
	protected $_expandedLoaded;
	/**
	 * @var string This is the full-path where the comic is temporally
	 * expanded in order to be shown in the web page. For example:
	 *	- /var/www/wcomic/cache/expanded/1/0c9610e6361e88791b85c132ddecdac4
	 */
	protected $_expandedPath;
	/**
	 * @var string This is similar to $_expandedPath but is only the URI to the
	 * expanded directory.
	 * It is a full path relative to the server. For example:
	 *	- /wcomic/cache/expanded/1/0c9610e6361e88791b85c132ddecdac4
	 */
	protected $_expandedUri;
	/**
	 * Class basic constructor
	 */
	public function __construct() {
		//
		// Calling the parent class constructor.
		parent::__construct();
		//
		// Clearing all paths, URIs and flags.
		// @{
		$this->_expandedLoaded = false;
		$this->_expandedPath = false;
		$this->_expandedUri = false;
		$this->_previewLoaded = false;
		$this->_previewDirPath = false;
		$this->_previewDirUri = false;
		$this->_previewPath = false;
		$this->_previewUri = false;
		// @}
	}
	/**
	 * Class basic destructor.
	 */
	public function __destruct() {
		//
		// This class has nothing to clean before deleting.
	}
	//
	// Public methods.
	/**
	 * This method allows to load and retrieve the collection where this comic
	 * is held.
	 *
	 * @return WCCollection Returns a collection pointer. When the comic has
	 * no collection or if there where a problem retrieving it, it returns
	 * a null pointer.
	 */
	public function collection() {
		//
		// Checking if the comic belongs to a collection.
		if(!$this->_collection) {
			//
			// Loading the collections holder.
			global $wcCollectionsHolder;
			//
			// Asking for the collection to the holder.
			$this->_collection = $wcCollectionsHolder->item($this->collection, $this->_group);
			//
			// If the collection is found and it has no issues, it can
			// be returned, otherwise, returns null.
			if(!$this->_collection || !$this->_collection->ok()) {
				$this->_collection = null;
			}
		}
		//
		// Returning the found collection.
		return $this->_collection;
	}
	/**
	 * This method allows to get a pointer to the directory where the comic is
	 * located.
	 *
	 * @return WCDirectory Returns a directory pointer.
	 */
	public function directory() {
		//
		// Checking id the pointer was not already loaded.
		if(!$this->_directory) {
			//
			// Loading the directories holder.
			global $wcDirectoriesHolder;
			//
			// Asking for the directory to the holder.
			$this->_directory = $wcDirectoriesHolder->item($this->directory, $this->_group);
			//
			// If the directory is found and it has no issues it can
			// be returned, otherwise, the pointer remains null.
			if(!$this->_directory || !$this->_directory->ok()) {
				$this->_directory = null;
			}
		}
		// Returning the found pointer.
		return $this->_directory;
	}
	/**
	 * This method expand all of this comic contents into the expansion
	 * directory.
	 *
	 * @return boolean Returns true when the comic was successfully expanded.
	 */
	public function expand() {
		//
		// Checking that this comic has no issues, that it is not lost and
		// that it's not already expanded.
		if($this->ok() && !$this->isExpanded() && !$this->lost) {
			//
			// Loading global default settings.
			// @{
			global $wcDefaults;
			global $wcPaths;
			// @}
			//
			// Creating thumbs directory.
			/** @todo this path must be get with the proper method. */
			$previewDirectory = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-thumbs-dirname"]}/{$this->_group->id}{$this->thumbs}");
			//
			// Checking if the directory must be physically created.
			if(!is_dir($previewDirectory)) {
				//
				// Physically creating the directory.
				mkdir($previewDirectory, $wcDefaults['directory-permissions'], true);
			}
			//
			// Checking if the expansion directory must be physically
			// created.
			if(!is_dir($this->expandedPath())) {
				//
				// Physically creating the directory.
				mkdir($this->expandedPath(), $wcDefaults['directory-permissions'], true);
			}
			//
			// Building the full path of the comic.
			/** @todo this path must be get with the proper method. */
			$comicPath = WCSanitizer::SystemPath("{$this->root()}{$this->directory()->name}/{$this->name}");
			//
			// Checking that it is possible to read the file.
			if(is_readable($comicPath)) {
				//
				// Loading global pointers.
				global $wcCompressors;
				//
				// Getting the basename of the comic.
				$comic = basename($comicPath);
				//
				// Starting a variable to create the extraction
				// command.
				$cmd = "";
				//
				// Searching for a command to expand this comic.
				$found = false;
				foreach($wcCompressors as $type => $compressor) {
					if(preg_match($compressor["pattern"], $comic)) {
						$found = $type;
						break;
					}
				}
				//
				// Checking if a command was found.
				if($found) {
					$cmdBin = getBinaryFullPath($wcCompressors[$found]["binary"]);
					//
					// Checking if the command to run has a
					// valid binary file.
					if($cmdBin != "") {
						//
						// Building expansion command
						// based on the extension of the
						// comic.
						$cmd = $wcCompressors[$found]["cmd-skeleton"];
						$cmd = str_replace("{COMMAND}", $cmdBin, $cmd);
						$cmd = str_replace("{COMIC}", $comicPath, $cmd);
						$cmd = str_replace("{DIRECTORY}", $this->expandedPath(), $cmd);
						//
						// Running the command in the
						// system. All generated output
						// will be dropped.
						/** @todo on the installation it must be checked that exec is allowed before
						 * installing.*/
						ob_start();
						exec($cmd);
						$cmdOutput = ob_get_clean();
						if($wcDefaults["debug"]) {
							if(!defined("__SHELL__")) {
								echo "<!--\n";
							}

							echo "Comand: {$cmd}\n";
							echo "results:\n{$cmdOutput}\n";

							if(!defined("__SHELL__")) {
								echo "-->\n";
							}
						}
					} else {
						//
						// Removing physical directory to avoid
						// further errors.
						rmdir($this->expandedPath());
						//
						// If no command was generated an
						// exception is raised.
						$pathVar = DIRECTORY_SEPARATOR == "/" ? "\$PATH" : "%PATH%";
						trigger_error(__CLASS__.": Command '{$wcCompressors[$found]["binary"]}' couldn't be transformed into a valid full path (check your '{$pathVar}')", E_USER_ERROR);
					}
				} else {
					//
					// Removing physical directory to avoid
					// further errors.
					rmdir($this->expandedPath());
					//
					// If no command was generated an
					// exception is raised.
					trigger_error(__CLASS__.": Unable to expand comic '{$comic}'", E_USER_ERROR);
				}
			}
		}
		//
		// The result of this method is the same one of 'isExpanded()'.
		return $this->isExpanded();
	}
	/**
	 * This method allows to get the system full path where all elements
	 * extracted from the comic will be temporarily stored.
	 *
	 * @return string Returns a system full path.
	 */
	public function expandedPath() {
		//
		// Loading paths.
		$this->loadExpanded();
		//
		// Returning the path.
		return $this->_expandedPath;
	}
	/**
	 * This method allows to get the URI where all elements extracted from the
	 * comic will be temporarily stored.
	 *
	 * @return string Returns a URI relative to the server.
	 */
	public function expandedUri() {
		//
		// Loading paths.
		$this->loadExpanded();
		//
		// Returning the URI.
		return $this->_expandedUri;
	}
	/**
	 * This method generates the comic preview.
	 *
	 * @return string returns the full path of the comic preview image.
	 */
	public function generateThumb() {
		//
		// Checking that there no issues with this comic and that it's
		// preview is not yet generated.
		if($this->ok() && !$this->hasPreview()) {
			//
			// Loading global default settings.
			global $wcDefaults;
			//
			// This operation requires the comic to be expanded.
			$this->expand();
			//
			// Loading the comic's list of images.
			$this->loadImages();
			//
			// If there's at least one image in the list, it creates
			// the preview.
			if(isset($this->_images[0])) {
				//
				// Getting the extension of the preview to
				// generate.
				$ext = $this->_images[0]->ext;
				//
				// If the preview extension set on the database
				// differs, it is changed.
				if($ext != $this->thumbs_ext) {
					//
					// Setting the new in extension in the
					// database.
					$this->thumbs_ext = $ext;
				}
				//
				// Building the preview path.
				$preview = $this->previewPath();
				//
				// Copying the first image of the list to the
				// preview path.
				if(copy($this->_images[0]->path, $preview)) {
					//
					// Cropping the new image to the global
					// previews size.
					if(!cropImage($preview, $wcDefaults['thumbs-size'])) {
						//
						// If there were a problem
						// cropping the image, it is
						// remove to avoid further
						// problems.
						@unlink($this->previewPath());
					}
				}
			}
			//
			// Setting to false the flag that controls preview paths.
			$this->_previewLoaded = false;
			//
			// Loading preview paths.
			$this->loadPreview();
		}
		//
		// Returning the generated path.
		return $this->_previewPath;
	}
	/**
	 * This method allows to know if the comic has a preview.
	 *
	 * @return boolean Returns true when the comic preview exists.
	 */
	public function hasPreview() {
		//
		// Loading preview's paths.
		$this->loadPreview();
		//
		// Returns true when the preview image exists.
		return is_file($this->previewPath());
	}
	/**
	 * This method allows to get the list of images contained by this comic.
	 *
	 * @return object[] Returns the list of images.
	 */
	public function images() {
		//
		// Loading the list of images.
		$this->loadImages();
		//
		// Returning the list.
		return $this->_images;
	}
	/**
	 * This method allows to if the comic is already expanded.
	 *
	 * @return boolean Returns true when the expansion directory exists.
	 */
	public function isExpanded() {
		return is_dir($this->expandedPath());
	}
	/**
	 * This method load the a comic based on its information saved in the
	 * table '%comics'.
	 *
	 * @param int $id Identificator for the comic.
	 * @param WCGroup $group Group that contains the comic to load.
	 * @return boolean Retruns true when there were no problems loading the
	 * comic.
	 */
	public function load($id, WCGroup &$group) {
		//
		// Because comic object loading may require may require more than
		// one try to get all the information, this flag indicates if
		// loadings should keep going on.
		$keepLoading = true;
		while($keepLoading) {
			//
			// By default, it is considered that only one try is
			// enough.
			$keepLoading = false;
			//
			// Calling the 'load()' method of parent class
			// WCGTableRepresentation
			parent::load($id, $group);
			//
			// Checking if the database entry loaded without any
			// problem and it represents valid comic
			if($this->ok()) {
				//
				// If there are neither a thumbnails URI nor a
				// expanded URI, these must be generated.
				if($this->id !== false && (!$this->thumbs || !$this->expanded)) {
					//
					// Creating a hash code based on
					// properties of the current comic.
					$code = md5("{$this->_group->id}|{$this->directory()->name}|{$this->name}");
					//
					// Setting a URI for expansion directory.
					$this->expanded = "/{$code}";
					//
					// Setting a URI for thumbnails directory.
					$this->thumbs = "/{$code[0]}/{$code[1]}{$code[2]}{$code[3]}/{$code}";
					//
					// At this point, many properties were
					// modified on the database and to avoid
					// inconsistencies, loading must be
					// retried.
					$keepLoading = true;
				}
				//
				// Setting the comic root directory with the group
				// root path.
				$this->_root = $this->_group->root();
				//
				// Checking some issues:
				//	- A not null id means the parent class
				//	  loaded the comic without problems.
				//	- A value in the directory property means
				//	  the comic is not located in the limbus
				//	  O_o
				//	  Zero isn't a valid id either.
				//	- If the comic is flagged as lost, it is
				//	  no good for normal usage.
				$this->_ok = ($this->id !== false && $this->directory && !$this->lost);
			}
		}
		//
		// Return the same result than method 'ok()'.
		return $this->ok();
	}
	/**
	 * This method allows to get the name of the comic. It is different to
	 * directly use the property 'name' through the pseudo-method __get()
	 * because, in this case, if the comic is scheduled to be rename, it
	 * returns the name it will have.
	 * This provides a transparent use of the comic name beyond any renaming.
	 *
	 * @return string Returns the name of the represented comic.
	 */
	public function name() {
		//
		// Choosing and returning the right name.
		return $this->next_name ? $this->next_name : $this->name;
	}
	/**
	 * This method allows to get the full path to the directory where all
	 * comic thumbnails will be stored.
	 *
	 * @return string Returns a full path.
	 */
	public function previewDirPath() {
		//
		// Loading preview's paths.
		$this->loadPreview();
		//
		// Returning the full path.
		return $this->_previewDirPath;
	}
	/**
	 * This method is similar to 'previewDirPath()', but for the URI.
	 *
	 * @return string Returns a URI.
	 */
	public function previewDirUri() {
		//
		// Loading preview's paths.
		$this->loadPreview();
		//
		// Returning the URI.
		return $this->_previewDirUri;
	}
	/**
	 * This method allows to get the image full path of the comic's thumbnail.
	 *
	 * @return string Returns a full path.
	 */
	public function previewPath() {
		//
		// Loading preview's paths.
		$this->loadPreview();
		//
		// Returning the full path.
		return $this->_previewPath;
	}
	/**
	 * This method returns the name of certain image thumbnail.
	 *
	 * @param int $id Requested position in the comic's list of images.
	 * @return string Returns an image name. Otherwise, it returns false.
	 */
	public function previewNameFor($id) {
		//
		// Setting the default value to be returned.
		$out = false;
		//
		// Loading the comic's list of images.
		$this->loadImages();
		//
		// Checking that there's an image for the specified position.
		if(isset($this->_images[$id])) {
			//
			// Building the right hash name for the preview.
			$out = md5($this->_images[$id]->lpath).".{$this->_images[$id]->ext}";
		}
		//
		// Returning the name.
		return $out;
	}
	/**
	 * This method returns the name of certain image thumbnail.
	 *
	 * @param int $id Requested position in the comic's list of images.
	 * @return string Returns an image name. Otherwise, it returns false.
	 */
	public function previewPathFor($id) {
		//
		// Setting the default value to be returned.
		$out = false;
		//
		// Loading the comic's list of images.
		$this->loadImages();
		//
		// Checking that there's an image for the specified position.
		if(isset($this->_images[$id])) {
			//
			// Building the full path for the preview.
			$out = WCSanitizer::SystemPath("{$this->previewDirPath()}/{$this->previewNameFor($id)}");
		}
		//
		// Returning the full path.
		return $out;
	}
	/**
	 * This method is similar to 'previewPath()', but for the URI.
	 *
	 * @return string Returns a URI.
	 */
	public function previewUri() {
		global $wcDefaults;
		//
		// Loading preview's paths.
		$this->loadPreview();
		//
		// Returning the URI.
		return $this->hasPreview() ? $this->_previewUri : $wcDefaults['preview'];
	}
	/**
	 * This method allows to get the URI of certain comic image.
	 *
	 * @param int $id Image position in the list of images of this comic.
	 * @param boolean $generate This flag indicates that the preview image
	 * should or should not be auto-generated. By default, when it's not
	 * specified, it is considered as true.
	 * @return string Returns the URI to the preview. If the URI cannot be
	 * retrieved, it returns false.
	 */
	public function previewUriFor($id, $generate = true) {
		//
		// Loading the list of default settings.
		global $wcDefaults;
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking that there are no issues with the current comic.
		if($this->ok()) {
			//
			// Loading the list of images.
			$this->loadImages();
			//
			// Checking if the requested image position is available.
			if(isset($this->_images[$id])) {
				//
				// Building the preview name.
				$previeName = $this->previewNameFor($id);
				//
				// Checking if the preview physical file exists.
				if(is_file(WCSanitizer::SystemPath("{$this->previewDirPath()}/{$previeName}"))) {
					//
					// Setting the path to be returned.
					$out = "{$this->previewDirUri()}/{$previeName}";
				} else {
					//
					// At this point it seems the preview file
					// is not yet generated. Therefore, if it
					// was requested, it is generated.
					if($generate) {
						//
						// Checking if the comic is
						// expanded.
						if(!$this->isExpanded()) {
							//
							// In order to generate a
							// preview, the comic must
							// be expanded.
							$this->expand();
						}
						//
						// Copying the real image for the
						// requested position to the
						// preview path.
						if(copy(WCSanitizer::SystemPath("{$this->expandedPath()}{$this->_images[$id]->lpath}"), WCSanitizer::SystemPath("{$this->previewDirPath()}/{$previeName}"))) {
							//
							// Cropping the image to
							// the global thumbnail's
							// size.
							if(!cropImage(WCSanitizer::SystemPath("{$this->previewDirPath()}/{$previeName}"), $wcDefaults['thumbs-size'])) {
								//
								// If there where a problem cropping the image, it is remove to avoid further
								// problems.
								@unlink($this->previewPath());
							} else {
								//
								// If there were no problems copying and cropping the image, it's URI is returned.
								$out = "{$this->previewDirUri()}/{$previeName}";
							}
						}
					} else {
						//
						// It seems the preview doesn't
						// exists and it wasn't generated.
						// false is returned.
						$out = false;
					}
				}
			}
		}
		//
		// Returning the requested URI.
		return $out;
	}
	/**
	 * This method allows to get the group root path where the comic is
	 * physically stored.
	 *
	 * @return string Returns a full path to the group's directory.
	 */
	public function root() {
		return $this->_root;
	}
	/**
	 * This method allows to physically move a comic from a directory to
	 * another.
	 *
	 * Even though this operation can be done manually by any system user,
	 * this method will also keep all associations made with the comic.
	 * Otherwise, a manual operation will cause two things:
	 *	- the new path of the comic will be taken as a new comic.
	 *	- and the current comic will become lost.
	 * And all this thing will occur on the next cron execution. Meanwhile
	 * many undesired behaviors will be seen.
	 *
	 * @param WCDirectory $directory Directory where the comic will be moved
	 * to.
	 * @return boolean Returns true where the operation was performed without
	 * any issue.
	 */
	public function setDirectory(WCDirectory &$directory) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking that:
		//	- there are no issues with the current comic
		//	- there are no issues with the destination directory.
		//	- the comic and the destination directory belong to the
		//	  same group.
		//	- The comic is not already in the destination directory.
		if($this->ok() && $directory->ok() && $this->group == $directory->group && $directory->id() != $this->directory) {
			//
			// Setting that it is ok to continue with the operation.
			$ok = true;
			//
			// Creating an auxiliar pointer to the current comic
			// directory.
			$curDirectory = $this->directory();
			//
			// Building all needed paths.
			// @{
			$dirPathFrom = WCSanitizer::SystemPath("{$this->_group->root}{$curDirectory->name}");
			$dirPathTo = WCSanitizer::SystemPath("{$this->_group->root}{$directory->name}");
			$comicPathFrom = WCSanitizer::SystemPath("{$dirPathFrom}/{$this->name}");
			$comicPathTo = WCSanitizer::SystemPath("{$dirPathTo}/{$this->name}");
			// @}
			//
			// Checking if it's possible to write in the current
			// comic's directory.
			if($ok && !is_writable($dirPathFrom)) {
				//
				// If it's not possible, an exception is raised.
				trigger_error(__CLASS__."::".__FUNCTION__."(): Writting access denied on directory '{$dirPathFrom}'", E_USER_ERROR);
				$ok = false;
			}
			//
			// Checking if it's possible to write in the destination
			// directory.
			if($ok && !is_writable($dirPathTo)) {
				//
				// If it's not possible, an exception is raised.
				trigger_error(__CLASS__."::".__FUNCTION__."(): Writting access denied on directory '{$dirPathTo}'", E_USER_ERROR);
				$ok = false;
			}
			//
			// Checking if if the current comic file exits.
			if($ok && file_exists($comicPathTo)) {
				//
				// If not, an exception is raised.
				trigger_error(__CLASS__."::".__FUNCTION__."(): Destination comic '{$comicPathTo}' already exists", E_USER_ERROR);
				$ok = false;
			}
			//
			// If everything is ok, the comic is moved.
			if($ok) {
				//
				// Moving the comic.
				rename($comicPathFrom, $comicPathTo);
				//
				// Checking that the new path exists and the old
				// one doesn't.
				if(is_file($comicPathFrom) || !is_file($comicPathTo)) {
					//
					// If any of this didn't happened, an
					// exception is raised.
					trigger_error(__CLASS__."::".__FUNCTION__."(): Unable to move comic '{$this->name}'", E_USER_ERROR);
					$ok = false;
				}
			}
			//
			// If everything is ok, it means that the comic was
			// successfully moved
			if($ok) {
				//
				// Cleaning the expansion path associated with the
				// old path.
				@recursive_unlink($this->expandedPath());
				//
				// Cleaning thumbnails' paths associated with the
				// old path.
				@recursive_unlink($this->previewDirPath());
				//
				// Cleaning the comic thumbnail associated with
				// the old path.
				@unlink($this->previewPath());
				//
				// Since comic was moved destination directory's
				// previews are no longer valid.
				$directory->valid_preview = false;
				//
				// Since comic was moved destination directory's
				// counts should be increased in one.
				$directory->count++;
				//
				// Since comic was moved origin directory's
				// previews are no longer valid.
				$curDirectory->valid_preview = false;
				//
				// Since comic was moved origin directory's
				// counts should be decreased in one.
				$curDirectory->count--;
				//
				// If there'ss one, collection's previews are also
				// no longer valid.
				if($this->collection()) {
					$this->collection()->valid_preview = false;
				}
				//
				// Preparing a statement to update the comic.
				// @{
				$stmt = false;
				$stmtID = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtID)) {
					$stmt = $this->_db->get($stmtID);
				} else {
					$query = "update  {$this->_dbprefix}comics\n";
					// Current directory should be the new one.
					$query .= "set     cmc_directory = :directory_s,\n";
					// Thumbnails URI changed.
					$query .= "        cmc_thumbs    = '',\n";
					// Expansion URI changed.
					$query .= "        cmc_expanded  = '',\n";
					// Since some URIs changed, it should be
					// regenerated.
					$query .= "        cmc_generated = 'N'\n";
					$query .= "where   cmc_group     = :group\n";
					$query .= " and    cmc_id        = :comic\n";

					$stmt = $this->_db->prepare($stmtID, $query);
				}
				// @}
				//
				// Sending updates to the database.
				$stmt->execute(array(
					":directory_s" => $directory->id,
					":group" => $this->_group->id,
					":comic" => $this->id
				));
				//
				// Many properties of the comic had changed,
				// therefore, to avoid problems, it is reloaded.
				$out = $this->load($this->id, $this->_group);
			}
		}
		//
		// Returning the operation results.
		return $out;
	}
	/**
	 * This method performs the scheduled renaming operation.
	 *
	 * @return boolean Returns true when the operation was performed without
	 * issues. If the comic was not scheduled for renaming, it returns false.
	 */
	public function setNextName() {
		//
		// This is the default value to be returned.
		$ok = true;
		//
		// Generating full paths for origin and destination.
		$oldPath = WCSanitizer::SystemPath("{$this->root()}{$this->directory()->name}/{$this->name}");
		$newPath = WCSanitizer::SystemPath("{$this->root()}{$this->directory()->name}/{$this->next_name}");
		//
		// Making some preliminary checks:
		//	- If the comic wasn't scheduled, it is an error.
		//	- If the current full path isn't a file, it is an error.
		//	- If the destination full path is an existing file, it is
		//	  an error.
		$ok = $this->next_name != "" && is_file($oldPath) && !file_exists($newPath);
		//
		// If everything is ok, the file is renamed.
		if($ok) {
			//
			// Renaming the file.
			rename($oldPath, $newPath);
			//
			// Checking that the new file exists and the old one
			// doesn't.
			$ok = !is_file($oldPath) && is_file($newPath);
		}
		//
		// If the comic was successfully renamed
		if($ok) {
			//
			// Cleaning the expansion path associated with the old
			// name.
			@recursive_unlink($this->expandedPath());
			//
			// Cleaning thumbnails' paths associated with the old
			// name.
			@recursive_unlink($this->previewDirPath());
			//
			// Cleaning the comic thumbnail associated with the old
			// name.
			@unlink($this->previewPath());
			//
			// Since comic previews will be regenerated, its
			// directory's previews are no longer valid.
			$this->directory()->valid_preview = false;
			//
			// If there'ss one, collection's previews are also no
			// longer valid.
			if($this->collection()) {
				$this->collection()->valid_preview = false;
			}
			//
			// Preparing a statement to update the comic.
			// @{
			$stmt = false;
			$stmtID = __CLASS__."::".__FUNCTION__."[comics]";
			if($this->_db->has($stmtID)) {
				$stmt = $this->_db->get($stmtID);
			} else {
				$query = "update  {$this->_dbprefix}comics\n";
				// Current name should be the new one.
				$query .= "set     cmc_name      = :name_s,\n";
				// Comic is no longer scheduled for renaming.
				$query .= "        cmc_next_name = '',\n";
				// Thumbnails URI changed.
				$query .= "        cmc_thumbs    = '',\n";
				// Expansion URI changed.
				$query .= "        cmc_expanded  = '',\n";
				// Since some URIs changed, it should be
				// regenerated.
				$query .= "        cmc_generated = 'N',\n";
				// It's name changed, so, it must be re-indexed by
				// the search engine.
				$query .= "        cmc_indexed   = 'N'\n";
				$query .= "where   cmc_group = :group\n";
				$query .= " and    cmc_id    = :id\n";

				$stmt = $this->_db->prepare($stmtID, $query);
			}
			// @}
			//
			// Sending updates to the database.
			$stmt->execute(array(
				":name_s" => $this->next_name,
				":group" => $this->_group->id(),
				":id" => $this->id
			));
			//
			// Many properties of the comic had changed, therefore, to
			// avoid problems, it is reloaded.
			$ok = $this->load($this->id, $this->_group);
		}
		//
		// If it was successfully reloaded.
		if($ok) {
			$this->next_name = "";
		}
		//
		// Returning the process results.
		return $ok;
	}
	//
	// Protected methods.
	/**
	 * This method auto-sets the paths of the physical previews and their
	 * URIs.
	 */
	protected function loadPreview() {
		//
		// Checking that there are no issues with this object and that
		// this operation was not run before.
		if($this->ok() && !$this->_previewLoaded) {
			//
			// Loading global path settings.
			global $wcPaths;
			//
			// Setting full path and URI for the comic's thumbnails.
			$this->_previewDirPath = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-thumbs-dirname"]}/{$this->_group->id}{$this->thumbs}");
			$this->_previewDirUri = WCSanitizer::WComixPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-thumbs-dirname"]}/{$this->_group->id}{$this->thumbs}");
			//
			// Setting full path and URI for the comic's thumbnail.
			$this->_previewPath = WCSanitizer::SystemPath("{$this->_previewDirPath}.{$this->thumbs_ext}");
			$this->_previewUri = WCSanitizer::WComixPath("{$this->_previewDirUri}.{$this->thumbs_ext}");
			//
			// Setting the flag to "already done".
			$this->_previewLoaded = true;
		}
	}
	/**
	 * This method auto-sets the paths of the physical expanded directory and
	 * its URI.
	 */
	protected function loadExpanded() {
		//
		// Checking that there are no issues with this object and that
		// this operation was not run before.
		if($this->ok() && !$this->_expandedLoaded) {
			//
			// Loading global path settings.
			global $wcPaths;
			//
			// Setting full path and URI.
			$this->_expandedPath = WCSanitizer::SystemPath("{$wcPaths["cache-directory"]}/{$wcPaths["cache-expand-dirname"]}/{$this->_group->id}{$this->expanded}");
			$this->_expandedUri = WCSanitizer::SystemPath("{$wcPaths["cache-directory-uri"]}/{$wcPaths["cache-expand-dirname"]}/{$this->_group->id}{$this->expanded}");
			//
			// Setting the flag to "already done".
			$this->_expandedLoaded = true;
		}
	}
	/**
	 * This method loads the list of images contained by the comic.
	 */
	protected function loadImages() {
		//
		// Checking that images were not loaded yet.
		if(!isset($this->_images[0])) {
			//
			// Loading the global patterns settings.
			global $wcPatterns;
			//
			// This operation requires the comic to be expanded.
			$this->expand();
			//
			// Seeking all images of this comic.
			findPaths($this->expandedPath(), "", $this->_images, $wcPatterns["image-paths"], true);
		}
	}
	/**
	 * This is an internal method called by the pseudo-method __set()
	 *
	 * @param string $name Name of the parameter being modified.
	 * @param string $value Value to set to the property with name $name.
	 * @return boolean Returns true when it is alright to modify the property.
	 */
	protected function subSet($name, &$value) {
		//
		// When the property being modified is the class name, any search
		// index on it is no longer valid and it should be updated ASAP.
		if($name == "next_name") {
			$this->indexed = false;
		}
		//
		// There are no conditions in this method that my interrupt the
		// property change.
		return true;
	}
	//
	// Public class methods.
	/**
	 * This is a sorting method based on comic id and it can be used on usort
	 * and uasort functions.
	 *
	 * @param WCComic $a Left operand.
	 * @param WCComic $b Right operand.
	 * @return boolean Returns true when the id of $a is greater than $b's.
	 */
	public static function IdSort(WCComic &$a, WCComic &$b) {
		return $a->id > $b->id;
	}
	/**
	 * This is a sorting method based on comic names and it can be used on
	 * usort and uasort functions.
	 *
	 * @param WCComic $a Left operand.
	 * @param WCComic $b Right operand.
	 * @return boolean Returns true when the name of $a is greater than
	 * $b's. Since it uses method 'name()' it considers cmc_name and
	 * cmc_next_name as well.
	 */
	public static function NameSort(WCComic &$a, WCComic &$b) {
		return $a->name() > $b->name();
	}
}
?>